using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace Pbdms.NetworkDealer.Utilities
{
    /// <summary>
    /// this class's role is to download a file with given properties
    /// </summary>
    public class FileDownloader
    {
        /// <summary>
        /// name of the file
        /// </summary>
        string fileNameOnClient;
        String fileNameOnServer;
        /// <summary>
        /// size of the file
        /// </summary>
        long fileSize;
        /// <summary>
        /// ip address of the server
        /// </summary>
        IPAddress targetIP;
        /// <summary>
        /// id of the item of which the file is requested to download
        /// </summary>
        string itemId;
        /// <summary>
        /// amount of data has been transfered
        /// </summary>
        long rby;
        int perc = 0;
        //if after this amount of time, no additional data is downloaded, the program
        //should consider that it has failed to download
        const int TIME_TO_LEAVE = 15000;
        //amount of bytes has been downloaded and recorded each 15 secs
        //used to detect that an interruption has occured and cause file downloading failed.
        long preRby;
        /// <summary>
        /// create an instance
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="fileSize"></param>
        /// <param name="itemId"></param>
        /// <param name="targetIP"></param>
        public FileDownloader(string fileNameOnClient, string fileNameOnServer, long fileSize, string itemId, IPAddress targetIP)
        {
            this.itemId = itemId;
            this.targetIP = targetIP;
            this.fileNameOnClient = fileNameOnClient;
            this.fileSize = fileSize;
            this.fileNameOnServer = fileNameOnServer;

        }
        /// <summary>
        /// obtain the available port from which data is sent
        /// </summary>
        /// <param name="sktTcpServer"></param>
        /// <param name="ipEPListener"></param>
        /// <returns></returns>
        int getAvailablePort(ref Socket sktTcpServer,ref IPEndPoint ipEPListener)
        {   
            int grantedPort = 0;
            try
            {
                sktTcpServer.Bind(ipEPListener);
                grantedPort = NetworkDealer.DOWNLOAD_PORT1;
            }
            catch
            {
                sktTcpServer.Close();
                ipEPListener = new IPEndPoint(IPAddress.Any, NetworkDealer.DOWNLOAD_PORT2);
                sktTcpServer = new Socket(ipEPListener.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                try
                {
                    sktTcpServer.Bind(ipEPListener);
                    grantedPort = NetworkDealer.DOWNLOAD_PORT2;
                }
                catch
                {
                    sktTcpServer.Close();
                    ipEPListener = new IPEndPoint(IPAddress.Any, NetworkDealer.DOWNLOAD_PORT3);
                    sktTcpServer = new Socket(ipEPListener.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                    try
                    {
                        sktTcpServer.Bind(ipEPListener);
                        grantedPort = NetworkDealer.DOWNLOAD_PORT3;
                    }
                    catch
                    {                        
                    }
                }
            }
            return grantedPort;

        }
        /// <summary>
        /// start downloading the file
        /// </summary>
        public void download()
        {
            
            IPEndPoint ipEPListener = new IPEndPoint(IPAddress.Any, NetworkDealer.DOWNLOAD_PORT1);

            Socket sktTcpServer = new Socket(ipEPListener.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            //get available port number
            int grantedPort = getAvailablePort(ref sktTcpServer,ref ipEPListener);
            if (grantedPort ==0)
            {
                //all ports are busy or being used by another application
                Common.Global.showMsgOneButton("No port for downloading the file is available, please check ports "
                    + NetworkDealer.DOWNLOAD_PORT1 + ", " + NetworkDealer.DOWNLOAD_PORT2 + ", "
                    + NetworkDealer.DOWNLOAD_PORT3 + " for any free port before try again",'I');                
            }
            //if the access right to the port is granted
            else
            {
                //start listening
                sktTcpServer.Listen(int.MaxValue);
                //signal the server
                sendClientReadySignal(grantedPort);

                //start accepting
                Socket tcpClient;
                byte[] receiveBuffer = new byte[16384];
                while (true)
                {
                    try
                    {
                        tcpClient = sktTcpServer.Accept();
                        try
                        {
                            String filePath = Common.Global.getLibFolder() + "\\" + fileNameOnClient;

                            System.IO.FileStream fout = new FileStream(filePath, FileMode.Create, FileAccess.Write);
                            NetworkStream nfs = new NetworkStream(tcpClient);
                            long size = this.fileSize;
                            rby = 0;
                            System.Timers.Timer tmer = new System.Timers.Timer(TIME_TO_LEAVE);
                            tmer.Elapsed += new System.Timers.ElapsedEventHandler(tmer_Elapsed);
                            preRby = 0;
                            tmer.Start();
                            try
                            {
                                //loop till the Full bytes have been read
                                while (rby < size)
                                {
                                    byte[] buffer = new byte[1024];
                                    //Read from the Network Stream
                                    try
                                    {
                                        int i = nfs.Read(buffer, 0, buffer.Length);
                                        fout.Write(buffer, 0, (int)i);
                                        rby = rby + i;
                                        perc = (int)(((double)rby / (double)size) * 100.00);                                        
                                    }
                                    catch
                                    {
                                        break;
                                    }
                                }                                
                                fout.Close();
                            }
                            catch
                            {
                            }
                        }
                        catch
                        {
                        }
                        finally
                        {
                            tcpClient.Close();
                            sktTcpServer.Close();
                            if (rby == this.fileSize)
                            {
                                //finish reading
                            }
                            else
                            {
                                //failed to read the whole file

                            }
                        }
                    }
                    catch
                    {
                    }
                    finally
                    {
                        System.Threading.Thread.CurrentThread.Abort();
                        
                    }
                }                
            }        
        }

        void tmer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (preRby == rby)
            {
            }
            else
                //update
                preRby = rby;
        }

        /// <summary>
        /// send a signal to server informing that the client is ready
        /// </summary>
        /// <param name="onPortNumber"></param>
        private void sendClientReadySignal(int onPortNumber)
        {
            object[] args = new object[2];
            //message format
            string msg = NetworkDealer.DOWNLOAD_CLIENT_READY +" "+ NetworkDealer.ipAddMyComputer.ToString() +"," + onPortNumber +","+ itemId.ToString()+"," + this.fileNameOnServer;
            args[0] = msg;
            args[1] = this.targetIP;
            Sender s = new Sender(args);
            System.Threading.ThreadStart thsSendMessage = new System.Threading.ThreadStart(s.sendMessagetoNetwork);
            System.Threading.Thread thSendMessage = new System.Threading.Thread(thsSendMessage);
            thSendMessage.Start();            
        }
    }
}
